<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vue-input-number</title>
    <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css"
          integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u"
          crossorigin="anonymous">
    <style>
        [v-cloak] {
            visibility: hidden;
        }
    </style>
</head>
<body>
<div id="app" v-cloak>
    <input-number v-model="value" :max="10" :min="0"></input-number>
</div>
<div id="app-2" v-cloak>
    <input-number v-model="value" :step="10"></input-number>
</div>
<script src="https://unpkg.com/vue"></script>
<!--<script src="https://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js"-->
        <!--integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa"-->
        <!--crossorigin="anonymous"></script>-->
<script>
    Vue.component('input-number', {
        template: `
            <div class="input-number">
                <input type="text" :value="currentValue" @change="handleChange"
                @keyup.up="handleUp" @keyup.down="handleDown">
                <button @click="handleDown" :disabled="currentValue <= min">-</button>
                <button @click="handleUp" :disabled="currentValue >= max">+</button>
            </div>`,
        props: {
            max: {
                type: Number,
                default: Infinity,
            },
            min: {
                type: Number,
                default: -Infinity,
            },
            value: {
                type: Number,
                default: 0
            },
            step: {
                type: Number,
                default: 1
            }
        },
        data() {
            return {
                currentValue: this.value
            }
        },
        watch: {
            currentValue(val) {
                this.$emit('input', val);
                this.$emit('on-change', val);
            },
            value(val) {
                this.updateValue(val);
            }
        },
        methods: {
            updateValue(val) {
                if (val > this.max) val = this.max;
                if (val < this.min) val = this.min;
                this.currentValue = val;
            },
            handleDown() {
                if (this.currentValue <= this.min) return;
                this.currentValue -= this.step;
            },
            handleUp() {
                if (this.currentValue >= this.max) return;
                this.currentValue += this.step;
            },
            handleChange(event) {
                function isValueNumber(value) {
                    return (/(^-?[0-9]+\.{1}\d+$)|(^-?[1-9][0-9]*$)|(^-?0{1}$)/).test(value + '');
                }
                let val = event.target.value.trim();
                let max = this.max;
                let min = this.min;

                if (isValueNumber(val)) {
                    val = Number(val);
                    this.currentValue = val;

                    if (val > max) {
                        this.currentValue = max;
                    } else if (val < min) {
                        this.currentValue = min;
                    }
                } else {
                    event.target.value = this.currentValue;
                }
            }
        },
        mounted() {
            this.updateValue(this.value);
        }
    });

    let app = new Vue({
        el: '#app',
        data: {
            value: 5
        }
    });
    let app2 = new Vue({
        el: '#app-2',
        data: {
            value: 100
        }
    });

</script>
</body>
</html>